package com.zzyl.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.zzyl.base.PageResponse;
import com.zzyl.constant.CacheConstants;
import com.zzyl.dto.AlertRuleDto;
import com.zzyl.entity.AlertData;
import com.zzyl.entity.AlertRule;
import com.zzyl.enums.DeviceLocationTypeEnum;
import com.zzyl.mapper.AlertDataMapper;
import com.zzyl.mapper.AlertRuleMapper;
import com.zzyl.mapper.DeviceMapper;
import com.zzyl.mapper.UserRoleMapper;
import com.zzyl.service.AlertRuleService;
import com.zzyl.vo.AlertRuleVo;
import com.zzyl.vo.DeviceDataVo;
import com.zzyl.websocket.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.annotation.RCascade;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author sjqn
 */
@Slf4j
@Service
public class AlertRuleServiceImpl implements AlertRuleService {

    @Autowired
    private AlertRuleMapper alertRuleMapper;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private UserRoleMapper userRoleMapper;
    @Resource
    private DeviceMapper deviceMapper;

    @Resource
    private AlertDataMapper alertDataMapper;

    //设备维护人员的角色名称
    @Value("${zzyl.alert.deviceMaintainerRole}")
    private String deviceMaintainerRole;
    //超级管理员的角色名称
    @Value("${zzyl.alert.managerRole}")
    private String managerRole;

    /**
     * 新增报警规则
     * @param alertRuleDto 报警规则请求模型
     */
    @Override
    public void createRule(AlertRuleDto alertRuleDto) {
        AlertRule alertRule = BeanUtil.toBean(alertRuleDto, AlertRule.class);
        alertRuleMapper.insert(alertRule);
    }

    /**
     * 分页条件查询
     *
     * @param pageNum       页码
     * @param pageSize      页面大小
     * @param alertRuleName 报警规则名称
     * @param productKey    产品key
     * @param functionName  功能名称
     * @return 分页结果
     */
    @Override
    public PageResponse<AlertRuleVo> getAlertRulePage(Integer pageNum, Integer pageSize, String alertRuleName, String productKey, String functionName) {
        PageHelper.startPage(pageNum, pageSize);
        Page<AlertRuleVo> page = alertRuleMapper.page(alertRuleName, productKey, functionName);
        //添加提示
        page.getResult().forEach(alertRuleVo -> {
            String alertReason = CharSequenceUtil.format("{}{}{},持续{}个周期就报警", alertRuleVo.getFunctionName(), alertRuleVo.getOperator(), alertRuleVo.getValue(), alertRuleVo.getDuration());
            alertRuleVo.setRules(alertReason);
        });

        return PageResponse.of(page, AlertRuleVo.class);
    }

    /**
     * 删除报警
     *
     * @param id 报警规则id
     */
    @Override
    public void deleteAlertRule(Long id) {
        alertRuleMapper.deleteByPrimaryKey(id);
    }

    /**
     * 修改报警
     *
     * @param id           报警规则id
     * @param alertRuleDto 报警规则更新请求模型
     */
    @Override
    public void updateAlertRule(Long id, AlertRuleDto alertRuleDto) {
        AlertRule alertRule = BeanUtil.toBean(alertRuleDto, AlertRule.class);
        alertRuleMapper.updateByPrimaryKeySelective(alertRule);
    }

    /**
     * 查询报警信息
     *
     * @param id 报警规则id
     * @return 报警信息
     */
    @Override
    public AlertRuleVo readAlertRule(Long id) {
        AlertRule alertRule = alertRuleMapper.selectByPrimaryKey(id);
        return BeanUtil.toBean(alertRule, AlertRuleVo.class);
    }

    /**
     * 启用或禁用
     *
     * @param id     报警id
     * @param status 报警规则状态
     */
    @Override
    public void enableOrDisable(Long id, Integer status) {
        alertRuleMapper.updateStatus(id, status);
    }

    @Override
    public void alertFilter(DeviceDataVo deviceDataVo) {
        //获取设备上报数据时间，如果上报发生在1分钟前，为历史数据，不再处理
        LocalDateTime alarmTime = deviceDataVo.getAlarmTime();
        LocalDateTime now = LocalDateTime.now();
        long seconds = LocalDateTimeUtil.between(alarmTime, now).toSeconds();
//        if(seconds > 60){
//            //说明数据已经过时了,不需要进行处理
//            return;
//        }
        //查询当前设备绑定哪些规则   判断是否符合这些规则
        List<AlertRule> allDeviceRules = alertRuleMapper.selectByFunctionId(deviceDataVo.getFunctionId(), "-1", deviceDataVo.getProductKey());
        //当前设备的校验规则
        List<AlertRule> alertRules = alertRuleMapper.selectByFunctionId(deviceDataVo.getFunctionId(), deviceDataVo.getIotId(), deviceDataVo.getProductKey());
        //得出所有的校验规则
        Set<AlertRule> allRules = new HashSet<>();
        allRules.addAll(alertRules);
        alertRules.addAll(allDeviceRules);
        if(CollUtil.isEmpty(allRules)){
            //如果没有设备没有校验的规则  则不用进行处理
            return;
        }
        allRules.forEach(rule-> deviceDateAlarmHandler(deviceDataVo,rule));
    }

    private void deviceDateAlarmHandler(DeviceDataVo deviceDataVo, AlertRule rule) {
        //为什么要将deviceDataVo传入  因为校验规则中是需要deviceDataVo中的数据进行校验
        //第一条校验规则就是先校验是否在有效的时间范围内
        String alertEffectivePeriod = rule.getAlertEffectivePeriod();
        String[] time = alertEffectivePeriod.split("~");
        LocalTime alarmTime = deviceDataVo.getAlarmTime().toLocalTime();
        LocalTime startTime = LocalTime.parse(time[0]);
        LocalTime endTime = LocalTime.parse(time[1]);
        if (alarmTime.isBefore(startTime) || alarmTime.isAfter(endTime)){
            //不在报警生效范围内
            return;
        }
        //第二层检验就是检验数据是否到达阈值
        //设备id
        String iotId = deviceDataVo.getIotId();
        //从redis中获取数据，报警规则连续触发次数rediskey
        String aggCountKey = CacheConstants.ALERT_TRIGGER_COUNT_PREFIX +
                iotId + ":" + deviceDataVo.getFunctionId() + ":" + rule.getId();
        //第一次没有数据的时候  我直接去获取肯定是没有值的
        //先得到运算符
        String operator = rule.getOperator();
        Float value = rule.getValue();
        Float aFloat = Convert.toFloat(deviceDataVo.getDataValue());//前段传过来的值   跟阈值进行比较  满足条件其实就是要进行通知
        if ((">=".equals(operator) && aFloat >= value) ||("<".equals(operator) && aFloat < value)){
            //达到阈值要求  发送消息给指定的用户来修复问题

            //这个位置是应该已经发送了一次数据了    后面是判断是否在持续时间段内
        }else {
            //删除缓存中的数据
            stringRedisTemplate.delete(aggCountKey);
            return;
        }
        //第三次校验就是校验是否满足沉默周期时间段的消息
        String silentKey = CacheConstants.ALERT_SILENT_PREFIX +
                iotId + ":" + deviceDataVo.getFunctionId() + ":" + rule.getId();
        //发送请求之后  判断是否还需要发送告警数据  有两个条件  一个条件是需要持续周期大于3   一个是需要超出制定阈值的时间
        //相当于是检查上一条数据的key是否存在  存在说明还没有到指定的时间
        if(stringRedisTemplate.hasKey(silentKey)){
            //说明时间还在沉默周期内不用处理
            return;
        }
        Long count = stringRedisTemplate.opsForValue().increment(aggCountKey);
        if(count < rule.getDuration()){
            //没有持续周期次数  不用处理
            return;
        }

        //到这里才开始检验是否在持续周期内判断是否需要再次发送数据
        //如果key不存在则说明时间已经过了沉默时间可以继续发送告警请求了   从这里开始设置数量
        stringRedisTemplate.opsForValue().set(silentKey,"1",rule.getAlertSilentPeriod(), TimeUnit.MINUTES);
        //删除持续的周期次数
        stringRedisTemplate.delete(aggCountKey);

        //需要通知的人员的id
        List<Long> userIds = new ArrayList<>();
        //查询是否是老人的数据
        if(1 == rule.getAlertDataType()){
            //设备异常,查询角色为维修工人的id
            userIds.addAll(userRoleMapper.selectByRoleName(deviceMaintainerRole));
        }else {
            if(deviceDataVo.getLocationType() == DeviceLocationTypeEnum.FIXED_DEVICE.getOrdinal()){
                //说明是固定的设备、床位
                userIds.addAll(deviceMapper.selectNursingIdsByIotIdWithBed(iotId));
            }else {
                //随身设备
                userIds.addAll(deviceMapper.selectNursingIdsByIotIdWithElder(iotId));
            }

        }
        userIds.addAll(userRoleMapper.selectByRoleName(managerRole));
        //批量添加告警数据
        insertAlertData(deviceDataVo,rule,userIds);
    }

    @Resource
    private WebSocketServer webSocketServer;
    private List<AlertData> insertAlertData(DeviceDataVo deviceDataVo, AlertRule rule, List<Long> userIds) {
        //字符串拼接告警的原因
        String reason = CharSequenceUtil.format("{}{}{},持续{}个周期就报警", rule.getFunctionName(), rule.getOperator(), rule.getValue(), rule.getDuration());
        //创建alertData对象
        AlertData alertData = BeanUtil.toBean(deviceDataVo, AlertData.class);
        //告警规则的id
        alertData.setAlertRuleId(rule.getId());
        alertData.setAlertReason(reason);
        //告警的数据类型 0： 老人异常数据  1设备的异常数据
        alertData.setType(rule.getAlertDataType());
        //未处理
        alertData.setStatus(0);
        List<AlertData> list = userIds.stream().map(userid -> {
            //复制对象
            AlertData alertData1 = BeanUtil.toBean(alertData, AlertData.class);
            alertData1.setUserId(userid);
            return alertData1;
        }).collect(Collectors.toList());
        //批量添加数据到数据库中
        alertDataMapper.batchInsert(list);

        return list;
    }

}
